/*
Copyright (c)2009 Seesmic, Inc

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package com.seesmic.twhix
{
	//import com.hurlant.crypto.tls.*;
	
	import flash.events.*;
	import flash.net.Socket;
	import flash.utils.Timer;

	public class StreamSocket extends EventDispatcher
	{
		protected var callback:Function;
		public var buffer:String;
		private var getXML:RegExp;
		private var getTag:RegExp = /(?<=(\<))[a-zA-Z:]+/i;
		public var stream_started:Boolean = false;
		private var stream_string:String;
		private var stream_tag:String;
		//public var tlsclient:Object;
		public var socket:* = null;
		private var keep_alive_timer:Timer;
		//public var tlssocket:Object;
		public var tlssocket:Object;
		public var encrypted:Boolean = false;
		public var state:Object = new Object();
		public var host:String;
		public var port:uint;
		
		public var tlsEvent:Object;
		public var tlsSocket:Object;
		public var tlsConfig:Object;
		public var tlsEngine:Object;
		
		
		public function StreamSocket(host:String=null, port:int=0, callback:Function=null)
		{
			state['connected'] = false;
			this.callback = callback;
			this.socket = new Socket();
			this.host = host;
			this.port = port;
			configureListeners();
			//this.socket.connect(host, port);
		}
		
		public function connect():void {
			state['connected'] = false;
			socket.connect(host, port);
			trace('setting up timer');
			keep_alive_timer = new Timer(120000);
			keep_alive_timer.addEventListener(TimerEvent.TIMER, keepAlive);
			keep_alive_timer.start();
		}
		
		public function keepAlive(e:TimerEvent):void {
			trace('sending keepalive');
			send(' ');
		}
		
		public function reconnect():void {
			if(tlssocket) {
				tlssocket.cleanupEventListeners();
				tlssocket = null;
			}
			socket = new Socket();
			configureListeners();
			socket.connect(host, port);
		}
		
		public function setupTLS(event:Class, config:Class, engine:Class, socket:Class):void {
			this.tlsConfig = config;
			this.tlsEngine = engine;
			this.tlsEvent = event;
			this.tlsSocket = socket;
			trace('setup called');
		}
		
		public function startTLS(host:String):void {
			trace(tlsConfig);
			trace(tlsEngine);
			var clientConfig:Object = new this.tlsConfig(this.tlsEngine.CLIENT);
			encrypted = true;
			tlssocket = new tlsSocket();
			tlssocket.addEventListener(tlsEvent.READY, onTLSReady);
			unConfigureListeners();
            tlssocket.startTLS(socket, host, clientConfig);
            this.socket = tlssocket;
            configureListeners();
            reset();
		}
		
		private function onTLSReady(e:Object):void {
			dispatchEvent(new StreamEvent(StreamEvent.TLS_READY, false, false, e.clone()));
		}
		
		private function configureListeners():void
		{
			socket.addEventListener(Event.CLOSE, closeHandler);
			socket.addEventListener(Event.CONNECT, connectHandler);
			socket.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
			socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
			socket.addEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
			
			socket.addEventListener(Event.CLOSE, disconnectHandler);
			socket.addEventListener(IOErrorEvent.IO_ERROR, disconnectHandler);
			socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, disconnectHandler);
		}
		
		private function unConfigureListeners():void
		{
			socket.removeEventListener(Event.CLOSE, closeHandler);
			socket.removeEventListener(Event.CONNECT, connectHandler);
			socket.removeEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
			socket.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
			socket.removeEventListener(ProgressEvent.SOCKET_DATA, socketDataHandler);
			
			socket.removeEventListener(Event.CLOSE, disconnectHandler);
			socket.removeEventListener(IOErrorEvent.IO_ERROR, disconnectHandler);
			socket.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, disconnectHandler);
		}
		
		private function closeHandler(event:Event):void {
			// nothing to see here ATM
		}

		private function connectHandler(event:Event):void {
			dispatchEvent(new StreamEvent(StreamEvent.CONNECTED, false, false, event.clone()));
			state['connected'] = true;
		}

		private function ioErrorHandler(event:IOErrorEvent):void {
			// nothing to see here ATM
		}

		private function securityErrorHandler(event:SecurityErrorEvent):void {
			trace("securityErrorHandler: " + event);
		}

		public function socketDataHandler(event:ProgressEvent):void {
			recvBuffer();
		}
		
		public function disconnectHandler(event:Event):void {
			keep_alive_timer.stop();
			if(state['connected']) {
				state['connected'] = false;
				dispatchEvent(new StreamEvent(StreamEvent.DISCONNECTED, false, false, event.clone()));
			} else {
				dispatchEvent(new StreamEvent(StreamEvent.CONNECT_FAILED, false, false, event.clone()));
			}
		}
		
		public function send(data:String):void {
			trace("OUT: " + data);
			dispatchEvent(new StreamEvent(StreamEvent.COMM_OUT, false, false, null, data));
			socket.writeUTFBytes(data);
			socket.flush();
		}
		
		public function reset():void {
			stream_started = false;

		}
		
		private function recvBuffer():void { // an ugly hack because AS3 doesn't have an asynch XML parser
			var incoming:String = socket.readUTFBytes(socket.bytesAvailable);
			var tag:Array = new Array(); 
			trace("IN : " + incoming);
			dispatchEvent(new StreamEvent(StreamEvent.COMM_IN, false, false, null, incoming));
			buffer += incoming;
			if(!stream_started && buffer) { // if we're expecting a start of stream
				buffer = buffer.substring(buffer.search("\\?>") + 2) // strip off the xml rules
				tag = getTag.exec(buffer); // grab the tag
				if(tag && tag[0].search('stream') > -1) // confirm it really is a start of stream tag
				{
					// proccess stream
					stream_tag = tag[0];
					stream_string = "<" + buffer.substring(buffer.search(tag[0]), buffer.search('>')) + ">";
					buffer = buffer.substring(buffer.search('>') + 1); // yoink
					stream_started = true;
				}
			}
			var gotfulltag:Boolean = true;
			while(gotfulltag) {
				tag = getTag.exec(buffer);
				if(tag) {
					var completeXML:RegExp = new RegExp("(\<" + tag[0] + "([^(\>)]+?)(/\>))|((\<" + tag[0] + "(.+?)\</" + tag[0] + "\>)+?)", "s"); // pull out tag with this TODO:account for CDATA
					var xmlstr:Object = completeXML.exec(buffer);
					if(xmlstr) {
						buffer = buffer.substring(buffer.search(xmlstr[0]) + xmlstr[0].length);
						callback(stream_string + xmlstr[0] + "</" + stream_tag + ">");
					} else {
						gotfulltag = false;
					}
				} else {
					gotfulltag = false;
				}
			}
		}
	}
}
